﻿using Mozhi.Abp.Domain.AuthorCenter;
using Mozhi.Abp.Domain.BasicData;
using mozhi.smarterp.Finance.Enums;
using System;
using Volo.Abp;
using Mozhi.Abp.Core.Extensions;
using System.Collections.Generic;
using System.Linq;

namespace mozhi.smarterp.Finance.Receivables
{
    public class SaleReceivable : DocumentAggregateRoot
    {
        public DateTime DueDate { get; private set; }

        public ReceivableDocumentType Type { get; private set; }

        /// <summary>
        /// 立账模式
        /// </summary>
        public RecognitionModelType RecognitionModel { get; private set; }

        public Guid CustomerId { get; private set; }

        public Customer Customer { get; private set; }

        public Guid CurrencyId { get; private set; }

        public Currency Currency { get; private set; }

        public decimal ExchangeRate { get; private set; }

        public bool IncludeTax { get; private set; }

        /// <summary>
        /// 采购部门
        /// </summary>
        public Guid? SalerDepartmentId { get; private set; }

        public Department SalerDepartment { get; private set; }

        /// <summary>
        /// 采购员
        /// </summary>
        public Guid? SalePersonId { get; private set; }

        public Employee SalePerson { get; private set; }

        public Guid? PayemtnTermId { get; private set; }

        public decimal Amount { private set; get; }

        public decimal AmountFr { private set; get; }

        public decimal TotalAmount { private set; get; }

        public decimal TotalAmountFr { private set; get; }

        public List<SaleReceivableItem> Items { get; private set; }

        private SaleReceivable() { }

        public SaleReceivable(Guid id , string number , DateTime date , DateTime dueDate, ReceivableDocumentType type, RecognitionModelType recognitionModel, Customer customer,
            Currency currency, bool includeTax,  Department salerDepartment, Employee salePerson, Guid? payemtnTermId , string comment)
        {
            Id = id;
            Number = number;
            Date = date;

            DueDate = dueDate;
            Type = type;
            RecognitionModel = recognitionModel;

            Customer = customer;
            CustomerId = customer==null ? default : customer.Id;

            Currency = currency;
            CurrencyId = currency==null ? default : currency.Id;
            
            ExchangeRate = currency == null ? 0 : currency.ExchangeRate; ;
            IncludeTax = includeTax;

            SalerDepartment = salerDepartment;
            SalerDepartmentId = salerDepartment?.Id;
            
            SalePersonId = salePerson?.Id;
            SalePerson = salePerson;
            PayemtnTermId = payemtnTermId;
            Comment = comment;

            Validate();
        }

        public void Change(DateTime date, DateTime dueDate, ReceivableDocumentType type, RecognitionModelType recognitionModel, Customer customer,
            Currency currency, bool includeTax, Department salerDepartment, Employee salePerson, Guid? payemtnTermId, string comment)
        {
            Date = date;

            DueDate = dueDate;
            Type = type;
            RecognitionModel = recognitionModel;

            Customer = customer;
            CustomerId = customer == null ? default : customer.Id;

            Currency = currency;
            CurrencyId = currency == null ? default : currency.Id;

            ExchangeRate = currency == null ? 0 : currency.ExchangeRate; ;
            IncludeTax = includeTax;

            SalerDepartment = salerDepartment;
            SalerDepartmentId = salerDepartment?.Id;

            SalePersonId = salePerson?.Id;
            SalePerson = salePerson;
            PayemtnTermId = payemtnTermId;
            Comment = comment;

            Validate();
        }

        public void SetItems(List<SaleReceivableItem> items)
        {
            foreach (var item in items)
            {
                AddOrUpdateItem(item);
            }

            var ids = items.Select(x => x.Id).ToList();

            var removedItems = Items.Where(x => !ids.Contains(x.Id)).ToList();

            foreach (var item in removedItems)
            {
                Items.Remove(item);
            }

            CalculateAmount();
        }

        #region
        private void Validate()
        {
            Check.NotDefaultOrNull<Guid>(CustomerId , nameof(CustomerId));
            Check.NotDefaultOrNull<Guid>(CurrencyId, nameof(CurrencyId));
            Check.NotDefaultOrNull<decimal>(ExchangeRate , nameof(ExchangeRate));
        }

        private void CalculateAmount()
        {
            foreach (var item in Items)
            {
                item.CalculateAmount(ExchangeRate);
            }

            Amount = Items.Sum(x => x.Amount);
            AmountFr = Items.Sum(x => x.AmountFr);

            TotalAmount = Items.Sum(x => x.TotalAmount);
            TotalAmountFr = Items.Sum(x => x.TotalAmountFr);
        }

        private void AddOrUpdateItem(SaleReceivableItem item)
        {
            ValidateStatus();

            if (Type == ReceivableDocumentType.Standard)
            {
                //需要产品，并且费用项目不能填
                if (item.ExpenseItemId.HasValue)
                    throw new UserFriendlyException("标准应收不能填写费用项目", "100000");

                if (!item.ProductId.HasValue || item.Product == null)
                    throw new UserFriendlyException("标准应收产品不能为空", "100000");

                if (item.Product.Type != (int)ProductType.Stock)
                    throw new UserFriendlyException($"标准应收产品:{item.Product.Code}-{item.Product.Name}类型不是库存", "100000");

                if (!item.Product.Saleable)
                    throw new UserFriendlyException($"标准应收产品:{item.Product.Code}-{item.Product.Name}不可销售", "100000");
            }
            else if (Type == ReceivableDocumentType.Expense)
            {
                if (item.ProductId.HasValue)
                    throw new UserFriendlyException("费用应收不能填写产品", "100000");

                if (!item.ExpenseItemId.HasValue || item.ExpenseItem == null)
                    throw new UserFriendlyException("费用应收费用项目不能为空", "100000");
            }
            

            var match = Items.FirstOrDefault(x => x.Id == item.Id);

            if (match == null)
            {
                match = item;

                match.CalculateAmount(ExchangeRate);

                Items.Add(match);
            }
            else
            {
                match.Change(item.Product, item.ExpenseItem, item.BasicQuantity, item.Price, item.TaxRate, item.AmountFr, item.Remark);

                item.CalculateAmount(ExchangeRate);
            }
        }

        #endregion
    }

    public class SaleReceivableItem : DocumentEntity
    {
        public Guid? ProductId { get; private set; }

        public Product Product { get; private set; }

        public Guid? ExpenseItemId { get; private set; }

        public ExpenseItem ExpenseItem { get; private set; }

        /// <summary>
        /// 计价单位
        /// </summary>
        public Guid BasicUnitId { get; private set; }

        /// <summary>
        /// 计价数量
        /// </summary>
        public decimal BasicQuantity { get; private set; }

        public decimal Price { get; private set; }

        public decimal PriceIncludeTax { get; private set; }

        public decimal PriceExcludeTax { get; private set; }

        public Guid? TaxRateId { get; private set; }

        public TaxRate TaxRate { get; private set; }

        public decimal? TaxRateValue { get; private set; }

        public decimal TaxAmountFr { get; private set; }

        public decimal TaxAmount { get; private set; }

        public decimal TotalAmountFr { get; private set; }

        public decimal TotalAmount { get; private set; }

        private SaleReceivableItem() { }

        public SaleReceivableItem(Guid id, Product product, ExpenseItem expenseItem, decimal basicQuantity, decimal price, decimal? amountFr , TaxRate taxRate, string remark)
        {
            Id = id;

            Product = product;
            ProductId = product?.Id;

            ExpenseItem = expenseItem;
            ExpenseItemId = expenseItem?.Id;
            
            BasicUnitId = product==null ? default : product.BasicUnitId;
            BasicQuantity = basicQuantity;
            Price = price;

            AmountFr = amountFr ?? 0;

            TaxRate = taxRate;
            TaxRateId = taxRate?.Id;
            
            TaxRateValue = taxRate?.Rate;

            Remark = remark;

            Valdiate();
        }

        public void Change(Product product, ExpenseItem expenseItem, decimal basicQuantity, decimal price, TaxRate taxRate , decimal? amountFr , string remark)
        {
            Product = product;
            ProductId = product?.Id;

            ExpenseItem = expenseItem;
            ExpenseItemId = expenseItem?.Id;

            BasicUnitId = product == null ? default : product.BasicUnitId;
            BasicQuantity = basicQuantity;
            Price = price;

            AmountFr = amountFr ?? 0;

            TaxRate = taxRate;
            TaxRateId = taxRate?.Id;

            TaxRateValue = taxRate?.Rate;

            Remark = remark;

            Valdiate();
        }

        #region
        private void Valdiate()
        {
            if (ProductId.HasValue)
            {
                Check.NotDefaultOrNull<Guid>(BasicUnitId, nameof(BasicUnitId));
                Check.NotDefaultOrNull<decimal>(BasicQuantity, nameof(BasicQuantity));
                Check.NotDefaultOrNull<decimal>(Price, nameof(Price));
            }
            else if(ExpenseItemId.HasValue)
            {
                Check.NotDefaultOrNull<decimal>(AmountFr, nameof(AmountFr));
            }
        }

        public void CalculateAmount(decimal exchangeRate)
        {
            if (ProductId.HasValue)
            {
                PriceIncludeTax = TaxRate == null ? Price : TaxRate.GetIncludeTaxValue(Price, false);
                PriceExcludeTax = TaxRate == null ? Price : TaxRate.GetExcludeTaxValue(Price, false);

                AmountFr = (BasicQuantity * PriceExcludeTax).ToMoney();
                Amount = (AmountFr * exchangeRate).ToMoney();

                TotalAmountFr = (BasicQuantity * PriceIncludeTax).ToMoney();
                TotalAmount = (TotalAmountFr * exchangeRate).ToMoney();

                TaxAmountFr = TotalAmountFr - AmountFr;
                TaxAmount = TotalAmount - Amount;
            }
            else
            {
                Amount = (AmountFr * exchangeRate).ToMoney();
                TotalAmountFr = AmountFr;
                TotalAmount = Amount;

                TaxAmountFr = TotalAmountFr - AmountFr;
                TaxAmount = TotalAmount - Amount;
            }
        }
        #endregion
    }
}
